package threads;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * (显示锁) 可重入锁通信示例
 * <p>
 * 显示锁： 由于 JUC 锁加锁和解锁的方法都是通过 Java API 显示进行的，因此叫显示锁。
 * <p>
 * Java 对象锁（内置锁）还存在性能问题，在竞争激烈的情况下， Java 对象锁会膨胀为重量级锁（基于操作系统的 Mutex Lock实现），
 * 而重量级锁的线程阻塞和唤醒操作需要进程在用户态和内核态来回切换，导致性能非常低。所以，迫切需要提供一种新的锁来提升竞争激励
 * 场景下锁的性能。Java 显示锁就是为了解决这些 Java 对象锁功能问题、性能问题而生的。
 * <p>
 * 可重入锁：
 * 1、表示该锁支持一个线程对资源的重复加锁；
 * 2、在同一时刻只能有一个线程获取到锁；
 */
public class ReentrantCommunicationTest {
    // 创建一个显示锁
    // Lock 有公平和不公平，公平意思是等待线程先进先出（FIFO）有序, 不公平就是不保证有序
    static Lock lock = new ReentrantLock(); // 默认是不公平的锁
    // 获取一个显示锁绑定的 Condition 对象
    // 用于线程间进行通信，与 Object 对象的 wait、notify 方法相类似，不同的是Condition类可以实现选择性的通知，让通知更具针对性。
    // Condition 对象的作用是控制并处理线程的状态，它可以使线程处于等待状态，也可以让线程继续运行
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread waitThread = new Thread(new WaitTask(), "WaitTask");
        waitThread.start();

        Thread timeWaitTask = new Thread(new TimeWaitTask(), "TimeWaitTask");
        timeWaitTask.start();

        int wait = System.currentTimeMillis() % 2 == 0 ? 2 : 5;
        Print.tco(String.format("等待 %d 秒后启动 NotifyTask.", wait));
        Thread.sleep(wait * 1000);

        Thread notifyTask = new Thread(new NotifyTask(), "NotifyTask");
        notifyTask.start();
    }


    /**
     * 等待被唤醒的线程任务
     */
    static class WaitTask implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                Print.tco("我是线程等待方, 准备开始等待");
                condition.await();
                Print.tco("我是线程等待方, 等待被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                Print.tco("我是线程等待方, 已释放锁");
            }
        }
    }

    /**
     * 限时等待被唤醒的线程任务
     */
    static class TimeWaitTask implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                Print.tco("我是限时线程等待方, 只准备等待 3 秒钟");
                // 指定时间 time 等待超时后，如果没有被唤醒，线程将中断等待
                // 线程等待超时返回 false ，其他情况返回 true
                if (condition.await(3, TimeUnit.SECONDS)) {
                    Print.tco("我是限时线程等待方, 等待被唤醒");
                } else {
                    Print.tco("我是限时线程等待方, 我等的不耐烦啦，不等啦");
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                Print.tco("我是限时线程等待方, 已释放锁");
            }
        }
    }

    /**
     * 唤醒等待线程的任务
     */
    static class NotifyTask implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                Print.tco("我是线程通知方, 准备开始唤醒线程");
                // 当调用 signal() 方法后，JUC 会从 condition 对象等待队里中唤醒一个线程，当线程被唤醒后，
                // 将会重新尝试获得与 Condition 对象绑定的显示锁, 一旦强占成功将继续执行。
                condition.signal();
                Print.tco("我是线程通知方, 唤醒线程通知已经发出但还没有立马释放锁");
            } finally {
                lock.unlock();
                Print.tco("我是线程通知方, 已释放锁");
            }

        }
    }
}

class Print {
    public static void tco(String msg) {
        SimpleDateFormat sdf = new SimpleDateFormat("hh:mm.ss");
        String curThread = Thread.currentThread().getName();
        System.out.println(String.format("%s:[%s]: %s", sdf.format(new Date()), curThread, msg));
    }
}


